/* *
 *
 *  (c) 2009-2017 Highsoft, Black Label
 *
 *  License: www.highcharts.com/license
 *
 * */
"use strict";
import H from "../parts/Globals.js";

import U from "../parts/Utilities.js";
var attr = U.attr,
  extend = U.extend,
  isArray = U.isArray,
  isNumber = U.isNumber,
  isObject = U.isObject,
  objectEach = U.objectEach,
  pick = U.pick;

import chartNavigationMixin from "../mixins/navigation.js";

var doc = H.doc,
  win = H.win,
  addEvent = H.addEvent,
  merge = H.merge,
  fireEvent = H.fireEvent,
  PREFIX = "highcharts-";

// IE 9-11 polyfill for Element.closest():
function closestPolyfill(el, s) {
  var ElementProto = win.Element.prototype,
    elementMatches =
      ElementProto.matches ||
      ElementProto.msMatchesSelector ||
      ElementProto.webkitMatchesSelector,
    ret = null;

  if (ElementProto.closest) {
    ret = ElementProto.closest.call(el, s);
  } else {
    do {
      if (elementMatches.call(el, s)) {
        return el;
      }
      el = el.parentElement || el.parentNode;
    } while (el !== null && el.nodeType === 1);
  }
  return ret;
}

/**
 * @private
 * @interface bindingsUtils
 */
var bindingsUtils = {
  /**
   * Update size of background (rect) in some annotations: Measure, Simple
   * Rect.
   *
   * @private
   * @function bindingsUtils.updateRectSize
   *
   * @param {global.Event} event
   *        Normalized browser event
   *
   * @param {Highcharts.Annotation} annotation
   *        Annotation to be updated
   */
  updateRectSize: function (event, annotation) {
    var chart = annotation.chart,
      options = annotation.options.typeOptions,
      coords = chart.pointer.getCoordinates(event),
      width = coords.xAxis[0].value - options.point.x,
      height = options.point.y - coords.yAxis[0].value;

    annotation.update({
      typeOptions: {
        background: {
          width: chart.inverted ? height : width,
          height: chart.inverted ? width : height,
        },
      },
    });
  },

  /**
   * Get field type according to value
   *
   * @private
   * @function bindingsUtils.getFieldType
   *
   * @param {*} value
   *        Atomic type (one of: string, number, boolean)
   *
   * @return {string}
   *         Field type (one of: text, number, checkbox)
   */
  getFieldType: function (value) {
    return {
      string: "text",
      number: "number",
      boolean: "checkbox",
    }[typeof value];
  },
};

H.NavigationBindings = function (chart, options) {
  this.chart = chart;
  this.options = options;
  this.eventsToUnbind = [];
  this.container = doc.getElementsByClassName(this.options.bindingsClassName);
};

// Define which options from annotations should show up in edit box:
H.NavigationBindings.annotationsEditable = {
  // `typeOptions` are always available
  // Nested and shared options:
  nestedOptions: {
    labelOptions: ["style", "format", "backgroundColor"],
    labels: ["style"],
    label: ["style"],
    style: ["fontSize", "color"],
    background: ["fill", "strokeWidth", "stroke"],
    innerBackground: ["fill", "strokeWidth", "stroke"],
    outerBackground: ["fill", "strokeWidth", "stroke"],
    shapeOptions: ["fill", "strokeWidth", "stroke"],
    shapes: ["fill", "strokeWidth", "stroke"],
    line: ["strokeWidth", "stroke"],
    backgroundColors: [true],
    connector: ["fill", "strokeWidth", "stroke"],
    crosshairX: ["strokeWidth", "stroke"],
    crosshairY: ["strokeWidth", "stroke"],
  },
  // Simple shapes:
  circle: ["shapes"],
  verticalLine: [],
  label: ["labelOptions"],
  // Measure
  measure: ["background", "crosshairY", "crosshairX"],
  // Others:
  fibonacci: [],
  tunnel: ["background", "line", "height"],
  pitchfork: ["innerBackground", "outerBackground"],
  rect: ["shapes"],
  // Crooked lines, elliots, arrows etc:
  crookedLine: [],
};

// Define non editable fields per annotation, for example Rectangle inherits
// options from Measure, but crosshairs are not available
H.NavigationBindings.annotationsNonEditable = {
  rectangle: ["crosshairX", "crosshairY", "label"],
};

extend(H.NavigationBindings.prototype, {
  // Private properties added by bindings:

  // Active (selected) annotation that is editted through popup/forms
  // activeAnnotation: Annotation

  // Holder for current step, used on mouse move to update bound object
  // mouseMoveEvent: function () {}

  // Next event in `step` array to be called on chart's click
  // nextEvent: function () {}

  // Index in the `step` array of the current event
  // stepIndex: 0

  // Flag to determine if current binding has steps
  // steps: true|false

  // Bindings holder for all events
  // selectedButton: {}

  // Holder for user options, returned from `start` event, and passed on to
  // `step`'s' and `end`.
  // currentUserDetails: {}
  /**
   * Initi all events conencted to NavigationBindings.
   *
   * @private
   * @function Highcharts.NavigationBindings#initEvents
   */
  initEvents: function () {
    var navigation = this,
      chart = navigation.chart,
      bindingsContainer = navigation.container,
      options = navigation.options;

    // Shorthand object for getting events for buttons:
    navigation.boundClassNames = {};

    objectEach(options.bindings, function (value) {
      navigation.boundClassNames[value.className] = value;
    });

    // Handle multiple containers with the same class names:
    [].forEach.call(bindingsContainer, function (subContainer) {
      navigation.eventsToUnbind.push(
        addEvent(subContainer, "click", function (event) {
          var bindings = navigation.getButtonEvents(bindingsContainer, event);

          if (bindings) {
            navigation.bindingsButtonClick(
              bindings.button,
              bindings.events,
              event
            );
          }
        })
      );
    });

    objectEach(options.events || {}, function (callback, eventName) {
      if (H.isFunction(callback)) {
        navigation.eventsToUnbind.push(
          addEvent(navigation, eventName, callback)
        );
      }
    });

    navigation.eventsToUnbind.push(
      addEvent(chart.container, "click", function (e) {
        if (
          !chart.cancelClick &&
          chart.isInsidePlot(
            e.chartX - chart.plotLeft,
            e.chartY - chart.plotTop
          )
        ) {
          navigation.bindingsChartClick(this, e);
        }
      })
    );
    navigation.eventsToUnbind.push(
      addEvent(chart.container, "mousemove", function (e) {
        navigation.bindingsContainerMouseMove(this, e);
      })
    );
  },

  /**
   * Common chart.update() delegation, shared between bindings and exporting.
   *
   * @private
   * @function Highcharts.NavigationBindings#initUpdate
   */
  initUpdate: function () {
    var navigation = this;

    chartNavigationMixin.addUpdate(function (options) {
      navigation.update(options);
    }, this.chart);
  },

  /**
   * Hook for click on a button, method selcts/unselects buttons,
   * then calls `bindings.init` callback.
   *
   * @private
   * @function Highcharts.NavigationBindings#bindingsButtonClick
   *
   * @param {Highcharts.HTMLDOMElement} [button]
   *        Clicked button
   *
   * @param {object} [events]
   *        Events passed down from bindings (`init`, `start`, `step`, `end`)
   *
   * @param {global.Event} [clickEvent]
   *        Browser's click event
   */
  bindingsButtonClick: function (button, events, clickEvent) {
    var navigation = this,
      chart = navigation.chart;

    if (navigation.selectedButtonElement) {
      fireEvent(navigation, "deselectButton", {
        button: navigation.selectedButtonElement,
      });

      if (navigation.nextEvent) {
        // Remove in-progress annotations adders:
        if (
          navigation.currentUserDetails &&
          navigation.currentUserDetails.coll === "annotations"
        ) {
          chart.removeAnnotation(navigation.currentUserDetails);
        }
        navigation.mouseMoveEvent = navigation.nextEvent = false;
      }
    }

    navigation.selectedButton = events;
    navigation.selectedButtonElement = button;

    fireEvent(navigation, "selectButton", { button: button });

    // Call "init" event, for example to open modal window
    if (events.init) {
      events.init.call(navigation, button, clickEvent);
    }

    if (events.start || events.steps) {
      chart.renderer.boxWrapper.addClass(PREFIX + "draw-mode");
    }
  },
  /**
   * Hook for click on a chart, first click on a chart calls `start` event,
   * then on all subsequent clicks iterate over `steps` array.
   * When finished, calls `end` event.
   *
   * @private
   * @function Highcharts.NavigationBindings#bindingsChartClick
   *
   * @param {Highcharts.Chart} chart
   *        Chart that click was performed on.
   *
   * @param {global.Event} clickEvent
   *        Browser's click event.
   */
  bindingsChartClick: function (chartContainer, clickEvent) {
    var navigation = this,
      chart = navigation.chart,
      selectedButton = navigation.selectedButton,
      svgContainer = chart.renderer.boxWrapper;

    // Click outside popups, should close them and deselect the annotation
    if (
      navigation.activeAnnotation &&
      !clickEvent.activeAnnotation &&
      // Element could be removed in the child action, e.g. button
      clickEvent.target.parentNode &&
      // TO DO: Polyfill for IE11?
      !closestPolyfill(clickEvent.target, "." + PREFIX + "popup")
    ) {
      fireEvent(navigation, "closePopup");
      navigation.deselectAnnotation();
    }

    if (!selectedButton || !selectedButton.start) {
      return;
    }

    if (!navigation.nextEvent) {
      // Call init method:
      navigation.currentUserDetails = selectedButton.start.call(
        navigation,
        clickEvent
      );

      // If steps exists (e.g. Annotations), bind them:
      if (selectedButton.steps) {
        navigation.stepIndex = 0;
        navigation.steps = true;
        navigation.mouseMoveEvent = navigation.nextEvent =
          selectedButton.steps[navigation.stepIndex];
      } else {
        fireEvent(navigation, "deselectButton", {
          button: navigation.selectedButtonElement,
        });
        svgContainer.removeClass(PREFIX + "draw-mode");
        navigation.steps = false;
        navigation.selectedButton = null;
        // First click is also the last one:
        if (selectedButton.end) {
          selectedButton.end.call(
            navigation,
            clickEvent,
            navigation.currentUserDetails
          );
        }
      }
    } else {
      navigation.nextEvent(clickEvent, navigation.currentUserDetails);

      if (navigation.steps) {
        navigation.stepIndex++;

        if (selectedButton.steps[navigation.stepIndex]) {
          // If we have more steps, bind them one by one:
          navigation.mouseMoveEvent = navigation.nextEvent =
            selectedButton.steps[navigation.stepIndex];
        } else {
          fireEvent(navigation, "deselectButton", {
            button: navigation.selectedButtonElement,
          });
          svgContainer.removeClass(PREFIX + "draw-mode");
          // That was the last step, call end():
          if (selectedButton.end) {
            selectedButton.end.call(
              navigation,
              clickEvent,
              navigation.currentUserDetails
            );
          }
          navigation.nextEvent = false;
          navigation.mouseMoveEvent = false;
          navigation.selectedButton = null;
        }
      }
    }
  },
  /**
   * Hook for mouse move on a chart's container. It calls current step.
   *
   * @private
   * @function Highcharts.NavigationBindings#bindingsContainerMouseMove
   *
   * @param {Highcharts.HTMLDOMElement} container
   *        Chart's container.
   *
   * @param {global.Event} moveEvent
   *        Browser's move event.
   */
  bindingsContainerMouseMove: function (container, moveEvent) {
    if (this.mouseMoveEvent) {
      this.mouseMoveEvent(moveEvent, this.currentUserDetails);
    }
  },
  /**
   * Translate fields (e.g. `params.period` or `marker.styles.color`) to
   * Highcharts options object (e.g. `{ params: { period } }`).
   *
   * @private
   * @function Highcharts.NavigationBindings#fieldsToOptions
   *
   * @param {object} fields
   *        Fields from popup form.
   *
   * @param {object} config
   *        Default config to be modified.
   *
   * @return {object}
   *         Modified config
   */
  fieldsToOptions: function (fields, config) {
    objectEach(fields, function (value, field) {
      var parsedValue = parseFloat(value),
        path = field.split("."),
        parent = config,
        pathLength = path.length - 1;

      // If it's a number (not "forma" options), parse it:
      if (
        isNumber(parsedValue) &&
        !value.match(/px/g) &&
        !field.match(/format/g)
      ) {
        value = parsedValue;
      }

      // Remove empty strings or values like 0
      if (value !== "" && value !== "undefined") {
        path.forEach(function (name, index) {
          var nextName = pick(path[index + 1], "");

          if (pathLength === index) {
            // Last index, put value:
            parent[name] = value;
          } else if (!parent[name]) {
            // Create middle property:
            parent[name] = nextName.match(/\d/g) ? [] : {};
            parent = parent[name];
          } else {
            // Jump into next property
            parent = parent[name];
          }
        });
      }
    });
    return config;
  },
  /**
   * Shorthand method to deselect an annotation.
   *
   * @function Highcharts.NavigationBindings#deselectAnnotation
   */
  deselectAnnotation: function () {
    if (this.activeAnnotation) {
      this.activeAnnotation.setControlPointsVisibility(false);
      this.activeAnnotation = false;
    }
  },
  /**
   * Generates API config for popup in the same format as options for
   * Annotation object.
   *
   * @function Highcharts.NavigationBindings#annotationToFields
   *
   * @param {Highcharts.Annotation} annotation
   *        Annotations object
   *
   * @return {object}
   *         Annotation options to be displayed in popup box
   */
  annotationToFields: function (annotation) {
    var options = annotation.options,
      editables = H.NavigationBindings.annotationsEditable,
      nestedEditables = editables.nestedOptions,
      getFieldType = this.utils.getFieldType,
      type = pick(
        options.type,
        options.shapes && options.shapes[0] && options.shapes[0].type,
        options.labels && options.labels[0] && options.labels[0].itemType,
        "label"
      ),
      nonEditables =
        H.NavigationBindings.annotationsNonEditable[options.langKey] || [],
      visualOptions = {
        langKey: options.langKey,
        type: type,
      };

    /**
     * Nested options traversing. Method goes down to the options and copies
     * allowed options (with values) to new object, which is last parameter:
     * "parent".
     *
     * @private
     * @function Highcharts.NavigationBindings#annotationToFields.traverse
     *
     * @param {*} option
     *        Atomic type or object/array
     *
     * @param {string} key
     *        Option name, for example "visible" or "x", "y"
     *
     * @param {object} allowed
     *        Editables from H.NavigationBindings.annotationsEditable
     *
     * @param {object} parent
     *        Where new options will be assigned
     */
    function traverse(option, key, parentEditables, parent) {
      var nextParent;

      if (
        parentEditables &&
        nonEditables.indexOf(key) === -1 &&
        ((parentEditables.indexOf && parentEditables.indexOf(key)) >= 0 ||
          parentEditables[key] || // nested array
          parentEditables === true) // simple array
      ) {
        // Roots:
        if (isArray(option)) {
          parent[key] = [];

          option.forEach(function (arrayOption, i) {
            if (!isObject(arrayOption)) {
              // Simple arrays, e.g. [String, Number, Boolean]
              traverse(arrayOption, 0, nestedEditables[key], parent[key]);
            } else {
              // Advanced arrays, e.g. [Object, Object]
              parent[key][i] = {};
              objectEach(arrayOption, function (nestedOption, nestedKey) {
                traverse(
                  nestedOption,
                  nestedKey,
                  nestedEditables[key],
                  parent[key][i]
                );
              });
            }
          });
        } else if (isObject(option)) {
          nextParent = {};
          if (isArray(parent)) {
            parent.push(nextParent);
            nextParent[key] = {};
            nextParent = nextParent[key];
          } else {
            parent[key] = nextParent;
          }
          objectEach(option, function (nestedOption, nestedKey) {
            traverse(
              nestedOption,
              nestedKey,
              key === 0 ? parentEditables : nestedEditables[key],
              nextParent
            );
          });
        } else {
          // Leaf:
          if (key === "format") {
            parent[key] = [
              H.format(option, annotation.labels[0].points[0]).toString(),
              "text",
            ];
          } else if (isArray(parent)) {
            parent.push([option, getFieldType(option)]);
          } else {
            parent[key] = [option, getFieldType(option)];
          }
        }
      }
    }

    objectEach(options, function (option, key) {
      if (key === "typeOptions") {
        visualOptions[key] = {};
        objectEach(options[key], function (typeOption, typeKey) {
          traverse(
            typeOption,
            typeKey,
            nestedEditables,
            visualOptions[key],
            true
          );
        });
      } else {
        traverse(option, key, editables[type], visualOptions);
      }
    });

    return visualOptions;
  },

  /**
   * Get all class names for all parents in the element. Iterates until finds
   * main container.
   *
   * @function Highcharts.NavigationBindings#getClickedClassNames
   *
   * @param {Highcharts.HTMLDOMElement}
   *        Container that event is bound to.
   *
   * @param {global.Event} event
   *        Browser's event.
   *
   * @return {Array<string>}
   *         Array of class names with corresponding elements
   */
  getClickedClassNames: function (container, event) {
    var element = event.target,
      classNames = [],
      elemClassName;

    while (element) {
      elemClassName = attr(element, "class");
      if (elemClassName) {
        classNames = classNames.concat(
          elemClassName.split(" ").map(function (name) {
            // eslint-disable-line no-loop-func
            return [name, element];
          })
        );
      }
      element = element.parentNode;

      if (element === container) {
        return classNames;
      }
    }

    return classNames;
  },
  /**
   * Get events bound to a button. It's a custom event delegation to find all
   * events connected to the element.
   *
   * @function Highcharts.NavigationBindings#getButtonEvents
   *
   * @param {Highcharts.HTMLDOMElement}
   *        Container that event is bound to.
   *
   * @param {global.Event} event
   *        Browser's event.
   *
   * @return {object}
   *         Oject with events (init, start, steps, and end)
   */
  getButtonEvents: function (container, event) {
    var navigation = this,
      classNames = this.getClickedClassNames(container, event),
      bindings;

    classNames.forEach(function (className) {
      if (navigation.boundClassNames[className[0]] && !bindings) {
        bindings = {
          events: navigation.boundClassNames[className[0]],
          button: className[1],
        };
      }
    });

    return bindings;
  },
  /**
   * Bindings are just events, so the whole update process is simply
   * removing old events and adding new ones.
   *
   * @private
   * @function Highcharts.NavigationBindings#update
   */
  update: function (options) {
    this.options = merge(true, this.options, options);
    this.removeEvents();
    this.initEvents();
  },
  /**
   * Remove all events created in the navigation.
   *
   * @private
   * @function Highcharts.NavigationBindings#removeEvents
   */
  removeEvents: function () {
    this.eventsToUnbind.forEach(function (unbinder) {
      unbinder();
    });
  },
  destroy: function () {
    this.removeEvents();
  },
  /**
   * General utils for bindings
   *
   * @private
   * @name Highcharts.NavigationBindings#utils
   * @type {bindingsUtils}
   */
  utils: bindingsUtils,
});

H.Chart.prototype.initNavigationBindings = function () {
  var chart = this,
    options = chart.options;

  if (options && options.navigation && options.navigation.bindings) {
    chart.navigationBindings = new H.NavigationBindings(
      chart,
      options.navigation
    );
    chart.navigationBindings.initEvents();
    chart.navigationBindings.initUpdate();
  }
};

addEvent(H.Chart, "load", function () {
  this.initNavigationBindings();
});

addEvent(H.Chart, "destroy", function () {
  if (this.navigationBindings) {
    this.navigationBindings.destroy();
  }
});

addEvent(H.NavigationBindings, "deselectButton", function () {
  this.selectedButtonElement = null;
});

addEvent(H.Annotation, "remove", function () {
  if (this.chart.navigationBindings) {
    this.chart.navigationBindings.deselectAnnotation();
  }
});

// Show edit-annotation form:
function selectableAnnotation(annotationType) {
  var originalClick =
    annotationType.prototype.defaultOptions.events &&
    annotationType.prototype.defaultOptions.events.click;

  function selectAndshowPopup(event) {
    var annotation = this,
      navigation = annotation.chart.navigationBindings,
      prevAnnotation = navigation.activeAnnotation;

    if (originalClick) {
      originalClick.click.call(annotation, event);
    }

    if (prevAnnotation !== annotation) {
      // Select current:
      navigation.deselectAnnotation();

      navigation.activeAnnotation = annotation;
      annotation.setControlPointsVisibility(true);

      fireEvent(navigation, "showPopup", {
        annotation: annotation,
        formType: "annotation-toolbar",
        options: navigation.annotationToFields(annotation),
        onSubmit: function (data) {
          var config = {},
            typeOptions;

          if (data.actionType === "remove") {
            navigation.activeAnnotation = false;
            navigation.chart.removeAnnotation(annotation);
          } else {
            navigation.fieldsToOptions(data.fields, config);
            navigation.deselectAnnotation();

            typeOptions = config.typeOptions;

            if (annotation.options.type === "measure") {
              // Manually disable crooshars according to
              // stroke width of the shape:
              typeOptions.crosshairY.enabled =
                typeOptions.crosshairY.strokeWidth !== 0;
              typeOptions.crosshairX.enabled =
                typeOptions.crosshairX.strokeWidth !== 0;
            }

            annotation.update(config);
          }
        },
      });
    } else {
      // Deselect current:
      navigation.deselectAnnotation();
      fireEvent(navigation, "closePopup");
    }
    // Let bubble event to chart.click:
    event.activeAnnotation = true;
  }

  H.merge(true, annotationType.prototype.defaultOptions.events, {
    click: selectAndshowPopup,
  });
}

if (H.Annotation) {
  // Basic shapes:
  selectableAnnotation(H.Annotation);

  // Advanced annotations:
  objectEach(H.Annotation.types, function (annotationType) {
    selectableAnnotation(annotationType);
  });
}

H.setOptions({
  /**
   * @optionparent lang
   */
  lang: {
    /**
     * Configure the Popup strings in the chart. Requires the
     * `annotations.js` or `annotations-advanced.src.js` module to be
     * loaded.
     *
     * @since           7.0.0
     * @type            {Object}
     * @product         highcharts highstock
     */
    navigation: {
      /**
       * Translations for all field names used in popup.
       *
       * @product         highcharts highstock
       * @type            {Object}
       */
      popup: {
        simpleShapes: "Simple shapes",
        lines: "Lines",
        circle: "Circle",
        rectangle: "Rectangle",
        label: "Label",
        shapeOptions: "Shape options",
        typeOptions: "Details",
        fill: "Fill",
        format: "Text",
        strokeWidth: "Line width",
        stroke: "Line color",
        title: "Title",
        name: "Name",
        labelOptions: "Label options",
        labels: "Labels",
        backgroundColor: "Background color",
        backgroundColors: "Background colors",
        borderColor: "Border color",
        borderRadius: "Border radius",
        borderWidth: "Border width",
        style: "Style",
        padding: "Padding",
        fontSize: "Font size",
        color: "Color",
        height: "Height",
        shapes: "Shape options",
      },
    },
  },
  /**
   * @optionparent navigation
   * @product      highcharts highstock
   */
  navigation: {
    /**
     * A CSS class name where all bindings will be attached to. Multiple
     * charts on the same page should have separate class names to prevent
     * duplicating events.
     *
     * Default value of versions < 7.0.4 `highcharts-bindings-wrapper`
     *
     * @since     7.0.0
     * @type      {string}
     */
    bindingsClassName: "highcharts-bindings-container",
    /**
     * Bindings definitions for custom HTML buttons. Each binding implements
     * simple event-driven interface:
     *
     * - `className`: classname used to bind event to
     *
     * - `init`: initial event, fired on button click
     *
     * - `start`: fired on first click on a chart
     *
     * - `steps`: array of sequential events fired one after another on each
     *   of users clicks
     *
     * - `end`: last event to be called after last step event
     *
     * @type         {Highcharts.Dictionary<Highcharts.StockToolsBindingsObject>|*}
     * @sample       stock/stocktools/stocktools-thresholds
     *               Custom bindings in Highstock
     * @since        7.0.0
     * @product      highcharts highstock
     */
    bindings: {
      /**
       * A circle annotation bindings. Includes `start` and one event in
       * `steps` array.
       *
       * @type    {Highcharts.StockToolsBindingsObject}
       * @default {"className": "highcharts-circle-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}}
       */
      circleAnnotation: {
        /** @ignore */
        className: "highcharts-circle-annotation",
        /** @ignore */
        start: function (e) {
          var coords = this.chart.pointer.getCoordinates(e),
            navigation = this.chart.options.navigation,
            controlPoints = [
              {
                positioner: function (target) {
                  var xy = H.Annotation.MockPoint.pointToPixels(
                      target.points[0]
                    ),
                    r = target.options.r;

                  return {
                    x:
                      xy.x + r * Math.cos(Math.PI / 4) - this.graphic.width / 2,
                    y:
                      xy.y +
                      r * Math.sin(Math.PI / 4) -
                      this.graphic.height / 2,
                  };
                },
                events: {
                  // TRANSFORM RADIUS ACCORDING TO Y
                  // TRANSLATION
                  drag: function (e, target) {
                    var annotation = target.annotation,
                      position = this.mouseMoveToTranslation(e);

                    target.setRadius(
                      Math.max(
                        target.options.r + position.y / Math.sin(Math.PI / 4),
                        5
                      )
                    );

                    annotation.options.shapes[0] =
                      annotation.userOptions.shapes[0] = target.options;

                    target.redraw(false);
                  },
                },
              },
            ];

          return this.chart.addAnnotation(
            merge(
              {
                langKey: "circle",
                shapes: [
                  {
                    type: "circle",
                    point: {
                      xAxis: 0,
                      yAxis: 0,
                      x: coords.xAxis[0].value,
                      y: coords.yAxis[0].value,
                    },
                    r: 5,
                    controlPoints: controlPoints,
                  },
                ],
              },
              navigation.annotationsOptions,
              navigation.bindings.circleAnnotation.annotationsOptions
            )
          );
        },
        /** @ignore */
        steps: [
          function (e, annotation) {
            var point = annotation.options.shapes[0].point,
              x = this.chart.xAxis[0].toPixels(point.x),
              y = this.chart.yAxis[0].toPixels(point.y),
              inverted = this.chart.inverted,
              distance = Math.max(
                Math.sqrt(
                  Math.pow(inverted ? y - e.chartX : x - e.chartX, 2) +
                    Math.pow(inverted ? x - e.chartY : y - e.chartY, 2)
                ),
                5
              );

            annotation.update({
              shapes: [
                {
                  r: distance,
                },
              ],
            });
          },
        ],
      },
      /**
       * A rectangle annotation bindings. Includes `start` and one event
       * in `steps` array.
       *
       * @type    {Highcharts.StockToolsBindingsObject}
       * @default {"className": "highcharts-rectangle-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}}
       */
      rectangleAnnotation: {
        /** @ignore */
        className: "highcharts-rectangle-annotation",
        /** @ignore */
        start: function (e) {
          var coords = this.chart.pointer.getCoordinates(e),
            navigation = this.chart.options.navigation,
            x = coords.xAxis[0].value,
            y = coords.yAxis[0].value,
            controlPoints = [
              {
                positioner: function (annotation) {
                  var xy = H.Annotation.MockPoint.pointToPixels(
                    annotation.shapes[0].points[2]
                  );

                  return {
                    x: xy.x - 4,
                    y: xy.y - 4,
                  };
                },
                events: {
                  drag: function (e, target) {
                    var coords = this.chart.pointer.getCoordinates(e),
                      x = coords.xAxis[0].value,
                      y = coords.yAxis[0].value,
                      shape = target.options.shapes[0],
                      points = shape.points;

                    // Top right point
                    points[1].x = x;
                    // Bottom right point (cursor position)
                    points[2].x = x;
                    points[2].y = y;
                    // Bottom left
                    points[3].y = y;

                    target.options.shapes[0].points = points;

                    target.redraw(false);
                  },
                },
              },
            ];

          return this.chart.addAnnotation(
            merge(
              {
                langKey: "rectangle",
                shapes: [
                  {
                    type: "path",
                    points: [
                      {
                        xAxis: 0,
                        yAxis: 0,
                        x: x,
                        y: y,
                      },
                      {
                        xAxis: 0,
                        yAxis: 0,
                        x: x,
                        y: y,
                      },
                      {
                        xAxis: 0,
                        yAxis: 0,
                        x: x,
                        y: y,
                      },
                      {
                        xAxis: 0,
                        yAxis: 0,
                        x: x,
                        y: y,
                      },
                    ],
                  },
                ],
                controlPoints: controlPoints,
              },
              navigation.annotationsOptions,
              navigation.bindings.rectangleAnnotation.annotationsOptions
            )
          );
        },
        /** @ignore */
        steps: [
          function (e, annotation) {
            var points = annotation.options.shapes[0].points,
              coords = this.chart.pointer.getCoordinates(e),
              x = coords.xAxis[0].value,
              y = coords.yAxis[0].value;

            // Top right point
            points[1].x = x;
            // Bottom right point (cursor position)
            points[2].x = x;
            points[2].y = y;
            // Bottom left
            points[3].y = y;

            annotation.update({
              shapes: [
                {
                  points: points,
                },
              ],
            });
          },
        ],
      },
      /**
       * A label annotation bindings. Includes `start` event only.
       *
       * @type    {Highcharts.StockToolsBindingsObject}
       * @default {"className": "highcharts-label-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}}
       */
      labelAnnotation: {
        /** @ignore */
        className: "highcharts-label-annotation",
        /** @ignore */
        start: function (e) {
          var coords = this.chart.pointer.getCoordinates(e),
            navigation = this.chart.options.navigation,
            controlPoints = [
              {
                symbol: "triangle-down",
                positioner: function (target) {
                  if (!target.graphic.placed) {
                    return {
                      x: 0,
                      y: -9e7,
                    };
                  }

                  var xy = H.Annotation.MockPoint.pointToPixels(
                    target.points[0]
                  );

                  return {
                    x: xy.x - this.graphic.width / 2,
                    y: xy.y - this.graphic.height / 2,
                  };
                },

                // TRANSLATE POINT/ANCHOR
                events: {
                  drag: function (e, target) {
                    var xy = this.mouseMoveToTranslation(e);

                    target.translatePoint(xy.x, xy.y);

                    target.annotation.labels[0].options = target.options;

                    target.redraw(false);
                  },
                },
              },
              {
                symbol: "square",
                positioner: function (target) {
                  if (!target.graphic.placed) {
                    return {
                      x: 0,
                      y: -9e7,
                    };
                  }

                  return {
                    x: target.graphic.alignAttr.x - this.graphic.width / 2,
                    y: target.graphic.alignAttr.y - this.graphic.height / 2,
                  };
                },

                // TRANSLATE POSITION WITHOUT CHANGING THE
                // ANCHOR
                events: {
                  drag: function (e, target) {
                    var xy = this.mouseMoveToTranslation(e);

                    target.translate(xy.x, xy.y);

                    target.annotation.labels[0].options = target.options;

                    target.redraw(false);
                  },
                },
              },
            ];

          return this.chart.addAnnotation(
            merge(
              {
                langKey: "label",
                labelOptions: {
                  format: "{y:.2f}",
                },
                labels: [
                  {
                    point: {
                      xAxis: 0,
                      yAxis: 0,
                      x: coords.xAxis[0].value,
                      y: coords.yAxis[0].value,
                    },
                    overflow: "none",
                    crop: true,
                    controlPoints: controlPoints,
                  },
                ],
              },
              navigation.annotationsOptions,
              navigation.bindings.labelAnnotation.annotationsOptions
            )
          );
        },
      },
    },
    /**
     * Path where Highcharts will look for icons. Change this to use icons
     * from a different server.
     *
     * @type      {string}
     * @default   https://code.highcharts.com/8.0.0/gfx/stock-icons/
     * @since     7.1.3
     * @apioption navigation.iconsURL
     */

    /**
     * A `showPopup` event. Fired when selecting for example an annotation.
     *
     * @type      {Function}
     * @apioption navigation.events.showPopup
     */

    /**
     * A `closePopup` event. Fired when Popup should be hidden, for example
     * when clicking on an annotation again.
     *
     * @type      {Function}
     * @apioption navigation.events.closePopup
     */

    /**
     * Event fired on a button click.
     *
     * @type      {Function}
     * @sample    highcharts/annotations/gui/
     *            Change icon in a dropddown on event
     * @sample    highcharts/annotations/gui-buttons/
     *            Change button class on event
     * @apioption navigation.events.selectButton
     */

    /**
     * Event fired when button state should change, for example after
     * adding an annotation.
     *
     * @type      {Function}
     * @sample    highcharts/annotations/gui/
     *            Change icon in a dropddown on event
     * @sample    highcharts/annotations/gui-buttons/
     *            Change button class on event
     * @apioption navigation.events.deselectButton
     */

    /**
     * Events to communicate between Stock Tools and custom GUI.
     *
     * @since        7.0.0
     * @product      highcharts highstock
     * @optionparent navigation.events
     */
    events: {},
    /**
     * Additional options to be merged into all annotations.
     *
     * @sample stock/stocktools/navigation-annotation-options
     *         Set red color of all line annotations
     *
     * @type      {Highcharts.AnnotationsOptions}
     * @extends   annotations
     * @exclude   crookedLine, elliottWave, fibonacci, infinityLine,
     *            measure, pitchfork, tunnel, verticalLine
     * @apioption navigation.annotationsOptions
     */
    annotationsOptions: {},
  },
});
